package com.nulldev.util.web.HttpClient;

import java.io.InputStream;
import java.io.Reader;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.JVM.JVM;
import com.nulldev.util.JVM.reflections.Reflections;
import com.nulldev.util.concurrency.threadIt.v4.emapi.ExecutorManager;
import com.nulldev.util.concurrency.threadIt.v4.emapi.IExecutorManager;
import com.nulldev.util.data.Arrays.maps.FastMaps;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.manage.Version;
import com.nulldev.util.web.HttpClient.clients.fuzzer.FuzzedProvider;
import com.nulldev.util.web.HttpClient.clients.impld.NativeProvider;
import com.nulldev.util.web.HttpClient.clients.legacy.LegacyProvider;
import com.nulldev.util.web.HttpClient.clients.nWS.nWSProvider;
import com.nulldev.util.web.HttpClient.providers.HttpClientProvider;
import com.nulldev.util.web.HttpClient.providers.websockets.HttpWebSocketRequest;
import com.nulldev.util.web.URLUtils.URLUtils;
import com.nulldev.util.web.flexibleKit.scfk.httpclient.SCFKProvider;

public class HttpClient {

	/**
	 * libHttpClient.
	 * 
	 * (X = Present, - = Not present, ~ = Partial support/Partially, + = Supported
	 * but not implemented)
	 * 
	 * Implementation Status: HTTP/1 HTTP/2 HTTP/3 WS Stable JRE Notes backport.
	 * <br>
	 * X X - X YES >=8 The improved mess, which is now way faster than the older
	 * version. A backport of JRE 10/11's HttpClient. Used by default if
	 * OKHTTP/Native is not available. WARNING: PLANNED REMOVAL AT VERSION 1.3.x
	 * native. X X - + ~ >=11 Must be enabled manually. WARNING: PLANNED REMOVAL AT
	 * VERSION 1.3 legacy. X - - - YES ALL Used as fallback. SCFK X - - - ~ ALL
	 * Replacement for SCWK. Semi-stable, work in progress. OKHTTP3 X X* ~** X YES
	 * >=8 Just works(TM), used by default if present. nWS X - - X ~ ALL Acts as a
	 * fallback for WebSockets only. (uses HTTP/1.1)
	 * 
	 * Planned implementations: kwik ~ - X - N/A >=8 Requires TLS 1.3, ETA: nullUtil
	 * 3.1/3.2 (NOTE: Current implementation is out-of-date.)
	 * 
	 * (* -> Requires JRE 9 or newer or BouncyCastle) <br>
	 * (** -> Requires kwik to actually operate on the protocol) <br>
	 * Notice: TLSv1.3 requires JRE 11 or newer. (or you manually add OpenJSSE but I
	 * can't be bothered) <br>
	 * - NOTE: nTube provides a external loading system for JRE 8 only. <br>
	 * - NOTE: Some pre-JRE 11 environments have patched in TLS 1.3 support, this is
	 * automatically detected and used.
	 * 
	 * TODO: <br>
	 * - Make HttpRequest<T>'s cloneable. <br>
	 * - (deadline: nullUtil 3.1/3.2) Add the kwik library as a provider for
	 * QUIC/HTTP3 <br>
	 * - [OKHTTP] Check if compression is in use.
	 * 
	 * Changelog: MOVED to CHANGELOG.
	 */

	public static final String VERSION = "1.2.9";
	public static final Version LIB_VERSION = new Version(VERSION);
	public static final String USER_AGENT = URLUtils.STANDARD_USERAGENT;
	public static final boolean DISABLE_STRING_STREAM_METHOD = Arguments.hasArgument("--HttpClient.disableSSM");
	public static final boolean FORCE_LEGACY_MODE = Arguments.hasArgument("--HttpClient.legacyOnly");
	public static final boolean ENABLE_NATIVE_MODE;
	public final static boolean ENABLE_SCFK = Arguments.hasArgument("--HttpClient.useSCFK");
	public final static IExecutorManager gld = ExecutorManager.get();

	static {
		if (JVM.version() >= 11f) {
			final RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
			final List<String> arguments = runtimeMxBean.getInputArguments();
			ENABLE_NATIVE_MODE = arguments.contains("--add-exports=java.net.http/jdk.internal.net.http=ALL-UNNAMED")
					&& arguments.contains("--illegal-access=warn"); // FIXME: Bypass this shit.
		} else {
			ENABLE_NATIVE_MODE = false;
		}
	}

	public static boolean supportsHTTP2() {
		return JVM.version() >= 1.8f;
	}

	private static final Map<String, HttpClientProvider> PROVIDERS = FastMaps.map(5);
	private static final AtomicReference<HttpClientProvider> standard_cached = new AtomicReference<HttpClientProvider>();
	private static final AtomicReference<HttpClientProvider> legacy_cached = new AtomicReference<HttpClientProvider>();
	private static final AtomicReference<HttpClientProvider> sockets_cached = new AtomicReference<HttpClientProvider>();

	static {
		if (PROVIDERS.isEmpty()) {
			if (Arguments.hasArgument("--HttpClient.fuzzer")) {
				PROVIDERS.put("nullUtil.fuzzer", new FuzzedProvider());
				LoggerFactory.getLogger().warn("Fuzzing is enabled! Expect ALL HttpClient requests to fail.");
			} else {
				if (JVM.version() >= 1.8f && Reflections.is("com.nulldev.util.internal.externals.okhttp.httpclient.OkHttpProvider"))
					PROVIDERS.put("okhttp.client",
							(HttpClientProvider) Reflections.make("com.nulldev.util.internal.externals.okhttp.httpclient.OkHttpProvider"));
				if (JVM.version() >= 11f && Reflections.is("com.nulldev.util.internal.externals.luminis.nu_httpclient.KwikProvider"))
					PROVIDERS.put("kwik.client",
							(HttpClientProvider) Reflections.make("com.nulldev.util.internal.externals.luminis.nu_httpclient.KwikProvider"));
				if (ENABLE_NATIVE_MODE && JVM.version() >= 11f)
					PROVIDERS.put("native", new NativeProvider());
				PROVIDERS.put("legacy", new LegacyProvider());
				if (JVM.version() >= 1.8f && Reflections.is("com.nulldev.util.internal.backport.httpclient_rw.httpclient.BackportProvider"))
					PROVIDERS.put("nullUtil.backport",
							(HttpClientProvider) Reflections.make("com.nulldev.util.internal.backport.httpclient_rw.httpclient.BackportProvider"));
				PROVIDERS.put("flexkit.scfk", new SCFKProvider());
				PROVIDERS.put("nullUtil.webSockets", new nWSProvider());
				if (FORCE_LEGACY_MODE) {
					standard_cached.set(legacy_cached.updateAndGet((e) -> {
						return PROVIDERS.get("legacy");
					}));
				}
			}
		}
	}

	private static final String[] PROVIDER_PRIORITY = new String[]
		{
				/* Default */
				"nullUtil.backport",

				"native", /* Deprecated */
				"legacy", "flexkit.scfk",

				/* Websockets */

				"nullUtil.webSockets",

				/* Misc. */

				"nullUtil.fuzzer",
				/* Disabled by default. */

				"okhttp.client", "kwik.client" };

	public static void main(String[] args) throws Exception {
		Arguments.setup(args);
		final HttpResponse<InputStream> s = (HttpResponse<InputStream>) PROVIDERS.get(JVM.version() >= 11f ? "kwik.client" : "flexkit.scfk").getStream()
				.url("https://www.google.com/").create();
		JVM.println("Response: " + s);
		final byte[] v = IOUtils.toByteArray(s.data());
		s.close();
		JVM.println(new String(v));
	}

	private static final HttpClientProvider get() {
		if (standard_cached.get() != null)
			return standard_cached.get();
		for (final String provider : PROVIDER_PRIORITY) {
			if (PROVIDERS.containsKey(provider))
				return (standard_cached.updateAndGet((e) -> {
					return PROVIDERS.get(provider);
				}));
		}
		throw new UnsupportedOperationException("No providers found!");
	}

	private static final HttpClientProvider getLegacy() {
		if (legacy_cached.get() != null)
			return legacy_cached.get();
		for (final String provider : PROVIDER_PRIORITY) {
			if (PROVIDERS.containsKey(provider)) {
				final HttpClientProvider pv = PROVIDERS.get(provider);
				if (pv.isLegacyCompatible())
					return legacy_cached.updateAndGet((e) -> {
						return pv;
					});
			}
		}
		throw new UnsupportedOperationException("No providers found!");
	}

	private static final HttpClientProvider getSockets() {
		if (sockets_cached.get() != null)
			return sockets_cached.get();
		for (final String provider : PROVIDER_PRIORITY) {
			if (PROVIDERS.containsKey(provider)) {
				final HttpClientProvider pv = PROVIDERS.get(provider);
				if (pv.providesWebSockets())
					return sockets_cached.updateAndGet((e) -> {
						return pv;
					});
			}
		}
		throw new UnsupportedOperationException("No providers found!");
	}

	public static HttpRequest<String> getString() {
		return get().getString();
	}

	public static HttpRequest<byte[]> getBytes() {
		return get().getBytes();
	}

	public static HttpRequest<InputStream> getStream() {
		return get().getStream();
	}

	public static HttpRequest<InputStream> getStream(final int flags) {
		if (flags == HttpClientFlags.FEATURE_FLAG_PREFER_QUIC && PROVIDERS.containsKey("kwik.client")) {
			return PROVIDERS.get("kwik.client").getStream();
		} else {
			return get().getStream();
		}
	}

	public static HttpRequest<InputStream> getStreamLegacy() {
		return getLegacy().getStream();
	}

	public static HttpRequest<Reader> getReader() {
		return get().getReader();
	}

	public static HttpRequest<Reader> getReaderLegacy() {
		return getLegacy().getReader();
	}

	public static HttpRequest<String> getStringLegacy() {
		return getLegacy().getString();
	}

	public static HttpWebSocketRequest getSocket() {
		return getSockets().getSocket();
	}

	public static HttpClientProvider getStandardBackend() {
		return get();
	}

	public static HttpClientProvider getLegacyBackend() {
		return getLegacy();
	}

	public static HttpClientProvider getWSBackend() {
		return getSockets();
	}
}
